#include <stdio.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <signal.h>
#include "wrap.h"
#include "pub.h"

#define PORT 8080



/**
 * 发送消息头
 * @param cfd
 * @param code
 * @param info
 * @param filetype
 * @param length
 */
void send_header(int cfd,int code,char *info,char * filetype,int length){
    // 状态行
    char buffer[1024] = "";
    int len = sprintf(buffer,"HTTP/1.1 %d %s\r\n",code,info);
    send(cfd,buffer,len,0);

    // 消息报头
    len = sprintf(buffer,"Content-Type:%s\r\n",filetype);
    send(cfd,buffer,len,0);
    if(length > 0){
        len = sprintf(buffer,"Content-Length:%d\r\n",length);
        send(cfd,buffer,len,0);
    }
    // 空行
    send(cfd,"\r\n",len,0);
}

/**
 * 发送文件
 * @param cfd
 * @param path
 * @param ev
 * @param epfd
 * @param flag
 */
void send_file(int cfd,char *path,struct epoll_event *ev,int epfd,int flag)
{
    int fd = open(path,O_RDONLY);
    if(fd <0)
    {
        perror("");
        return ;
    }
    char buf[1024]="";
    int len =0;
    while(1)
    {
        len = read(fd,buf,sizeof(buf));
        if(len < 0)
        {
            perror("");
            break;

        }
        else if(len == 0)
        {
            break;
        }
        else
        {
            int n=0;
            n =  send(cfd,buf,len,0);
            printf("len=%d\n", n);

        }
    }
    close(fd);
    //关闭cfd,下树
    if(flag==1)
    {
        close(cfd);
        epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,ev);
    }
}

/**
 * 读取客户端请求
 * @param ev
 * @param epfd
 */
void read_client_request(struct epoll_event * ev,int epfd){
    // 读取请求（先读取一行，再把其他行读取，然后扔掉）
    char buffer[1500] = "";
    char temp[1500] = "";
    int n = Readline(ev->data.fd,buffer, sizeof(buffer));
    //下树并关闭
    if(n <= 0){
        epoll_ctl(epfd,EPOLL_CTL_DEL,ev->data.fd,ev);
        close(ev->data.fd);
        printf("%d => close\n",ev->data.fd);
        return;
    }
    printf("%s",buffer);
    //读其他行 此处如果 FD 不设置非阻塞 会阻塞在这里，不会答应 read ok
    while (n = Readline(ev->data.fd,temp, sizeof(temp)) > 0);
    printf("%d read ok\n",ev->data.fd);
    // 判断是否为GET  GET /a.txt HTTP/1.1
    char method[256] = "";
    char content[256] = "";
    char protocol[256] = "";
    sscanf(buffer,"%[^ ] %[^ ] %[^ \\r\\n]",method,content,protocol);
    //printf(":%s :%s :%s \n",method,content,protocol);
    if(strcasecmp(method,"get") == 0){ // 忽略大小写对比
        // 得到浏览器请求的文件 默认请求 ./
        char * path = content + 1;
        strdecode(path,path);
        if(* path == 0){
            path = "./";
        }
        // 判断文件是否存在,
        struct stat s;
        // 不存在 发送 ERROR.HTML
        if(stat(path, &s) < 0){
            printf("none\n");
            //先发送 报头（状态行 消息头 空行）
            send_header(ev->data.fd,404,"NOT FOUND", get_mime_type("*.html"),0);
            //发送文件 error.html
            send_file(ev->data.fd,"error.html",ev,epfd,1);

        }
            // 存在（普通文件，目录）
        else{
            // 文件
            if(S_ISREG(s.st_mode)){
                printf("file\n");
                //先发送 报头（状态行 消息头 空行）
                send_header(ev->data.fd,200,"OK", get_mime_type(path),s.st_size);
                //发送文件
                send_file(ev->data.fd,path,ev,epfd,1);
            }
                // 目录
            else if(S_ISDIR(s.st_mode)){
                printf("dir\n");
                //发送一个列表  网页
                send_header(ev->data.fd, 200,"OK",get_mime_type("*.html"),0);
                //发送header.html
                send_file(ev->data.fd,"dir_header.html",ev,epfd,0);
                struct dirent **mylist=NULL;
                char buf[1024]="";
                int len =0;
                //              目录路径 存放结果首地址 过滤函数  排序函数
                int n = scandir(path,   &mylist,    NULL,   alphasort);// 读取目录下的文件
                for(int i=0;i<n;i++)
                {
                    //printf("%s\n", mylist[i]->d_name);
                    if(mylist[i]->d_type == DT_DIR)//如果是目录
                    {
                        len = sprintf(buf,"<li><a href=%s/ >%s</a></li>",mylist[i]->d_name,mylist[i]->d_name);
                    }
                    else
                    {
                        len = sprintf(buf,"<li><a href=%s >%s</a></li>",mylist[i]->d_name,mylist[i]->d_name);
                    }

                    send(ev->data.fd,buf,len ,0);

                    free(mylist[i]);
                }
                free(mylist);


                send_file(ev->data.fd,"dir_tail.html",ev,epfd,1);
            }
        }



    }


    //

}

/**
 * http协议
 *      http请求
 *          请求行
 *              请求方式 /请求内容 版本\r\n
 *          请求头
 *          空行 \r\n
 *          数据
 *      http应答
 *          状态行
 *              版本 状态码 状态信息 \r\n
 *          消息报头
 *              文件类型是必给的
 *              文件长度\r\n 可有可无
 *          空行
 *          数据
 *
 * epoll 实现 http server 遇到大文件时会出现发送不完全的问题。
 * @return
 */
int main() {
    signal(SIGPIPE,SIG_IGN);
    // 切换工作目录
    // 获取当前工作路径
    char work_path[256] = "";
    char * ori_path = getenv("PWD");
    strcpy(work_path,ori_path);
    strcat(work_path,"/web-http");
    chdir(work_path);

    // 创建套接字，绑定
    int socket_fd = tcp4bind(PORT,NULL);
    // 监听套接字
    Listen(socket_fd,128);
    // 创建 epoll 树
    int epfd = epoll_create(1);
    // 将 socket fd 上树
    struct epoll_event ev,evs[1024];
    ev.data.fd = socket_fd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd,EPOLL_CTL_ADD,socket_fd,&ev);

    while (1) {
        // epoll 循环监听
        int nready = epoll_wait(epfd,evs,1024,-1);
        if(nready < 0){
            perror("");
            break;
        }
        else{
            for (int i = 0; i < nready; ++i) {
                // socket fd 变化
                if(evs[i].data.fd == socket_fd && (evs[i].events & EPOLLIN)){
                    struct sockaddr_in client_addr;
                    char ip[16] = "";
                    socklen_t len = sizeof(client_addr);
                    // 提取 client fd 套接字
                    int client_fd = Accept(socket_fd,(struct sockaddr *)&client_addr,&len);
                    printf("client => %s : %d => connnect\n",
                           inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ip,16),
                           ntohs(client_addr.sin_port));
                    // 设置非阻塞
                    int flag = fcntl(client_fd,F_GETFL);
                    flag |= O_NONBLOCK;
                    fcntl(client_fd,F_SETFL,flag);
                    // client fd 上树
                    ev.data.fd = client_fd;
                    ev.events = EPOLLIN;
                    epoll_ctl(epfd,EPOLL_CTL_ADD,client_fd,&ev);
                }
                // client fd 变化
                else if(evs[i].events & EPOLLIN){
                    read_client_request(& evs[i],epfd);
                }
            }
        }
    }

    return 0;
}
